package web.security;

import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * Created by aceyin on 2015/5/31 0031.
 */
public class JWT {
    private static final Logger LOGGER = LoggerFactory.getLogger(JWT.class);
    private static final String KEY_ID = "key-dianjiang-auth";
    private static final String ISSUER = "DianJiang-app";
    private static final String AUDIENCE = "DianJiang-User";
    private static final int EXPIRE_TIME_IN_MINUTES = 24 * 60 * 365;
    private static final String SUBJECT = "dianjiang-user";
    private static final String USER_IDENTITY = "user_identity";
    private static RsaJsonWebKey jsonWebKey;
    private static String KEY_FILE_NAME = "/json-web-key.bin";

    static {
        try {
            jsonWebKey = loadJsonWebKey();
            jsonWebKey.setKeyId(KEY_ID);//TODO: what the keyId should be?
        } catch (Exception e) {
            LOGGER.error("Error while initialize jsonWebKey ", e);
        }
    }

    private static RsaJsonWebKey loadJsonWebKey() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(JWT.class.getResourceAsStream(KEY_FILE_NAME));
        Object o = ois.readObject();
        if (o == null || !(o instanceof RsaJsonWebKey)) {
            return null;
        }
        return (RsaJsonWebKey) o;
    }


    /**
     * Create a JWT.
     * <p>
     * From here: https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples
     *
     * @return
     */
    public static String create(long userid, String md5pwd) {
        try {
            JwtClaims claims = new JwtClaims();
            claims.setIssuer(ISSUER);  // who creates the token and signs it TODO: make this configurable
            claims.setAudience(AUDIENCE); // to whom the token is intended to be sent
            claims.setExpirationTimeMinutesInTheFuture(EXPIRE_TIME_IN_MINUTES); // time when the token will expire (10 minutes from now)
            claims.setGeneratedJwtId(); // a unique identifier for the token
            claims.setIssuedAtToNow();  // when the token was issued/created (now)
            claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
            claims.setSubject(SUBJECT); // the subject/principal is whom the token is about
            claims.setClaim(USER_IDENTITY, new JwtClaim(userid, md5pwd).toString()); // additional claims/attributes about the subject can be added
//            List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
//            claims.setStringListClaim("groups", groups); // multi-valued claims work too and will end up as a JSON array

            JsonWebSignature jws = new JsonWebSignature();
            // The payload of the JWS is JSON content of the JWT Claims
            jws.setPayload(claims.toJson());

            // The JWT is signed using the private key
            jws.setKey(jsonWebKey.getPrivateKey());

            // Set the Key ID (kid) header because it's just the polite thing to do.
            // We only have one key in this example but a using a Key ID helps
            // facilitate a smooth key rollover process
            jws.setKeyIdHeaderValue(jsonWebKey.getKeyId());

            // Set the signature algorithm on the JWT/JWS that will integrity protect the claims
            jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

            return jws.getCompactSerialization();
        } catch (JoseException e) {
            throw new SecurityException("Error while creating JWT token", e);
        }
    }

    /**
     * Get the userid from a given jwt string.
     *
     * @param jwt
     * @return
     */
    public static JwtClaim getIdentity(String jwt) {
        if (jwt == null) {
            return JwtClaim.EMPTY;
        }
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer(ISSUER) // whom the JWT needs to have been issued by
                .setExpectedAudience(AUDIENCE) // to whom the JWT is intended for
                .setVerificationKey(jsonWebKey.getKey()) // verify the signature with the public key
                .build(); // create the JwtConsumer instance

        try {
            //  Validate the JWT and process it to the Claims
            JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
            Object value = jwtClaims.getClaimValue(USER_IDENTITY);
            if (value != null) {
                return JwtClaim.from(String.valueOf(value));
            }
        } catch (InvalidJwtException e) {
            LOGGER.error("Error while parse JWT:", e);
        }
        return JwtClaim.EMPTY;
    }

}
